package main


import (
    "path/filepath"
    "github.com/fsnotify/fsnotify"
    "strings"
    "os"
    "container/list"
)

type TargetItem struct {
    base        string   //fullpath
    watchedDirs []string //fullpath
    tmpDirs     []string
    tempFiles   []string
}

func (w *TargetItem) contains(path string) bool {
    return strings.HasPrefix(path, w.base)
}

func (w *TargetItem) rel(path string) string {
    ret := ""
    if w.contains(path) {
        ret = strings.TrimPrefix(path, w.base)
    }
    return ret
}

func (w *TargetItem) Add(path string) {
    if w.contains(path) {
        w.tempFiles = append(w.tempFiles, strings.TrimPrefix(path, w.base))
    }
}
func (w *TargetItem) WalkAndAdd(path string, info os.FileInfo, err error) error {
    if info.IsDir() {
        w.tmpDirs = append(w.tmpDirs, path)
    } else {
        if fn := w.rel(path); fn != "" {
            w.tempFiles = append(w.tempFiles, strings.TrimPrefix(path, w.base))
        }
    }
    return nil
}

type Target struct {
    items          []TargetItem
    files          *list.List
    deleteFiles    []string
    createdFiles   []string
    createdDirs    []string
    NotifyHandlers []NotifyHandler
}

func NewTarget(dirs ...string) *Target {
    var i []TargetItem
    for _, d := range dirs {
        if !strings.HasSuffix(d, "/") {
            d = d + "/"
        }
        i = append(i, TargetItem{base: d})
    }
    return &Target{
        items:     i,
        files:     list.New(),
    }
}

func (tgt *Target) init() error {
    for _, d := range tgt.items {
        event := fsnotify.Event{
            Name: d.base,
            Op:   fsnotify.Create,
        }
        if err := tgt.handle(event); err != nil {
            return err
        }
    }
    return nil
}
func (tgt *Target) notify() error { //notify
    for _, h := range tgt.NotifyHandlers {
        if err := h(tgt.createdDirs, tgt.deleteFiles, tgt.createdFiles); err != nil {
            return err
        }
    }
    tgt.createdDirs = nil
    tgt.deleteFiles = nil
    tgt.createdFiles = nil
    return nil
}

type NotifyHandler func([]string, []string, []string) error

func (tgt *Target) handle(event fsnotify.Event) error { //if more to watch
    switch {
    case event.Op&fsnotify.Create == fsnotify.Create:
        fn := event.Name
        fi, err := os.Stat(fn)
        if err != nil {
            return err
        }
        if fi.IsDir() {
            for _, t := range tgt.items {
                if t.contains(fn) {
                    filepath.Walk(fn, t.WalkAndAdd)
                    if len(t.tmpDirs) > 0 {
                        tgt.createdDirs = append(tgt.createdDirs, t.tmpDirs...)
                        t.tmpDirs = nil
                    }
                    if len(t.tempFiles) > 0 {
                        tgt.createdFiles = append(tgt.createdFiles, t.tempFiles...)
                        for _, f := range t.tempFiles {
                            tgt.files.PushBack(f)
                        }
                        t.tempFiles = nil
                    }
                    break
                }
            }
        } else {
            for _, t := range tgt.items {
                if t.contains(fn) { //watched dir should not contian each(same level)
                    f := t.rel(fn)
                    tgt.createdFiles = append(tgt.createdFiles, f)
                    tgt.files.PushBack(f)
                    break
                }
            }
        }
    case event.Op&fsnotify.Rename == fsnotify.Rename || event.Op&fsnotify.Remove == fsnotify.Remove:
        var fn string
        for _, t := range tgt.items { //convert to relative path
            if fn = t.rel(event.Name); fn != "" {
                break
            }
        }
        for e := tgt.files.Front(); e != nil; e = e.Next() {
            if e.Value.(string) == fn {
                tgt.files.Remove(e)
                tgt.deleteFiles = append(tgt.deleteFiles, fn)
                break
            }
        }
    }
    return nil
}
